/**
 * libnlog -- User friendly logging system with network support.
 *
 * Copyright (C) 2011
 * 	Samuel Nussbaum <samuel (dot) nussbaum (at) e-robot.ch>
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 */
#include "Logger.hpp"

#include <iostream>

#include <boost/functional/hash/extensions.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/unordered_set.hpp>

#include <boost/date_time.hpp>

#include <boost/thread.hpp>

using namespace boost;

namespace nlog {

Mutex *Logger::m_mutex = NULL;

Logger::LoggerSet *Logger::m_instances = NULL;

Thread *Logger::m_thread = NULL;

Logger::MessageSet Logger::m_buffer;

const String Logger::m_kNames[8] = {
	"Finest",
	"Finer",
	"Fine",
	"Info",
	"Config",
	"Warning",
	"Error"
};

const String Logger::m_kColors[8] = {
	"\033[0;30m",
	"\033[1;30m",
	"\033[1;0m",
	"\033[1;36m",
	"\033[1;32m",
	"\033[1;33m",
	"\033[1;31m"
};

Logger::Logger(const String &_name) :
	m_level(Finest), m_hashName(hash_range(_name.begin(), _name.end())), m_name(_name),
	m_stream(&::std::cerr) {

}

Logger::Logger(const Logger &_other) :
	m_level(_other.m_level), m_hashName(_other.m_hashName), m_name(_other.m_name),
	m_stream(_other.m_stream) {

}

Logger::~Logger() {

}

Logger &Logger::instance(const String &_name) {
	if (m_instances == NULL) {
		m_instances = new LoggerSet;
		m_mutex = new Mutex;
		m_thread = new Thread(&Logger::run);
	}

	SizeT hashName = hash_range(_name.begin(), _name.end());
	LoggerSet::const_iterator logIt = m_instances->begin();
	while (logIt != m_instances->end()) {
		if ((*logIt)->m_hashName == hashName)
			break;
		logIt++;
	}

	if (logIt == m_instances->end()) {
		::std::pair<LoggerSet::const_iterator, bool> newIt;
		newIt = m_instances->insert(new Logger(_name));
		logIt = newIt.first;
	}

	return const_cast<Logger &>(*(*logIt));
}

void Logger::write(const Message &_message) const {
	if (_message.level() >= m_level) {
		m_mutex->lock();
		m_buffer.insert(_message);
		m_mutex->unlock();
	}
}

Logger &Logger::operator=(const Logger &_other) {
	m_hashName = _other.m_hashName;
	m_name = _other.m_name;
	m_level = _other.m_level;
	return *this;
}

void Logger::run() {
	static Destroyer destroyer; // Instantiate structure who indicate end of program.

	while (true) {
		Time current(get_system_time());
		m_mutex->lock();
		while (m_buffer.size() > 0 && (m_buffer.begin()->time() - current).total_seconds()) {
			const Message &message(*m_buffer.begin());
			OStream &stream = *instance(message.name()).m_stream;
			stream << m_kColors[0];
			stream << "[";
			stream << posix_time::to_iso_string(message.time()) << "]";
			stream << m_kColors[message.level()];
			stream.width(8);
			stream << m_kNames[message.level()] << " : " << message.content();
			stream << m_kColors[0];
			stream << " (" << message.name() << ")";
			stream << "\033[0m";
			stream << std::endl;
			m_buffer.erase(m_buffer.begin());
		}
		m_mutex->unlock();
		this_thread::sleep(posix_time::milliseconds(100));
	}
}

Logger::Destroyer::~Destroyer() {
	if (m_thread != NULL) {
		while (m_thread->joinable()) {
			m_thread->interrupt();
			m_thread->timed_join(posix_time::milliseconds(10));
		}
	}

	while (m_buffer.size() > 0) {
		const Message &message(*m_buffer.begin());
		OStream &stream = *instance(message.name()).m_stream;
		stream << m_kColors[0];
		stream << "[";
		stream << posix_time::to_iso_string(message.time()) << "]";
		stream << m_kColors[message.level()];
		stream.width(8);
		stream << m_kNames[message.level()] << " : " << message.content();
		stream << m_kColors[0];
		stream << " (" << message.name() << ")";
		stream << "\033[0m";
		stream << std::endl;
		m_buffer.erase(m_buffer.begin());
	}
}

Stream Logger::finest() {
	return Stream(Finest, *this);
}

Stream Logger::finer() {
	return Stream(Finer, *this);
}

Stream Logger::fine() {
	return Stream(Fine, *this);
}

Stream Logger::info() {
	return Stream(Info, *this);
}

Stream Logger::config() {
	return Stream(Config, *this);
}

Stream Logger::warning() {
	return Stream(Warning, *this);
}

Stream Logger::error() {
	return Stream(Error, *this);
}

SizeT Logger::hash() const {
	return m_hashName;
}

String Logger::name() const {
	return m_name;
}

LevelEnum Logger::level() const {
	return m_level;
}

void Logger::setLevel(LevelEnum _level) {
	m_level = _level;
}

}
